unit QImport3ASCII;

{$I QImport3VerCtrl.Inc}

interface

uses
  {$IFDEF QI_UNICODE}
    {$IFDEF VCL10} WideStrings, {$ELSE} QImport3WideStrings, {$ENDIF} QImport3GpTextFile,
  {$ENDIF}
  Windows, Classes, QImport3, SysUtils, IniFiles, QImport3EZDSLHsh, QImport3StrTypes;

type
  TQImport3ASCII = class(TQImport3)
  private
    FComma: AnsiChar;
    FQuote: AnsiChar;
    FEncoding: TQICharsetType;
    FCounter: Integer;
{$IFDEF QI_UNICODE}
    FBuffStrW: WideString;
    FFileW: TGpTextFile;
    FColumnsW: TWideStrings;
{$ELSE}
    FBuffStr: AnsiString;
    FFile: TextFile;
    FColumns: TStrings;
{$ENDIF}
    FColumnsHash: THashTable;
    FPosDataHash: THashTable;

    function HasComma: Boolean;
    procedure ReadColumns( const Str: qiString; AStrings: TqiStrings;
      AColumnsHash: THashTable);
  protected
    function CheckProperties: Boolean; override;

    procedure StartImport; override;
    function CheckCondition: Boolean; override;
    function Skip: Boolean; override;
    procedure FillImportRow; override;
    function ImportData: TQImportResult; override;
    procedure ChangeCondition; override;
    procedure FinishImport; override;

    procedure DoLoadConfiguration(IniFile: TIniFile); override;
    procedure DoSaveConfiguration(IniFile: TIniFile); override;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property FileName;
    property SkipFirstRows default 0;

(*    property Comma: Char read FComma write FComma;
    property Quote: Char read FQuote write FQuote;*)
    property Comma: AnsiChar read FComma write FComma;
    property Quote: AnsiChar read FQuote write FQuote;
    property Encoding: TQICharsetType read FEncoding
      write FEncoding default ctWinDefined;
  end;

implementation

uses
  QImport3StrIDs, DB, QImport3Common, QImport3WideStrUtils, QImport3EzdslBse;

type
  PPosData = ^TPosData;
  TPosData = record
    Pos: Integer;
    Len: Integer;
  end;

  PStrData = ^TStrData;
  TStrData = record
    Str: qiString;
  end;

procedure DisposePosHashData(AData: Pointer);
begin
  Dispose(AData);
end;

procedure DisposeColumnsHashData(AData: Pointer);
begin
  Finalize(PStrData(AData)^);
  Dispose(AData);
end;

{ TQImport3ASCII }

constructor TQImport3ASCII.Create(AOwner: TComponent);
begin
  inherited;
  SkipFirstRows := 0;
  FComma := AnsiChar(GetListSeparator);
  FQuote := '"';
  FEncoding := ctWinDefined;
end;

function TQImport3ASCII.CheckProperties: Boolean;
var
  i, j: Integer;
begin
  Result := inherited CheckProperties;
  if HasComma then // example field=2
  begin
    for i := 0 to Map.Count - 1 do
      try
        StrToInt(Map.Values[Map.Names[i]]);
      except
        on E: EConvertError do
          raise EQImportError.Create(QImportLoadStr(QIE_MapMissing));
      end;
  end
  else begin // example: field=3;5
    for i := 0 to Map.Count - 1 do
    begin
      j := Pos(';', Map.Values[Map.Names[i]]);
      if j = 0 then
        raise EQImportError.Create(QImportLoadStr(QIE_MapMissing))
      else begin
        try
          StrToInt(Copy(Map.Values[Map.Names[i]], 1, j - 1));
        except
          on E:EConvertError do
            raise EQImportError.Create(QImportLoadStr(QIE_MapMissing));
        end;
        try
          StrToInt(Copy(Map.Values[Map.Names[i]], j + 1, Length(Map.Values[Map.Names[i]]) - j));
        except
          on E:EConvertError do
            raise EQImportError.Create(QImportLoadStr(QIE_MapMissing));
        end;
      end;
    end;
  end
end;

procedure TQImport3ASCII.StartImport;
var
  i, k: Integer;
  pi: Pointer;
  str: string;
  posData: PPosData;
begin
{$IFDEF QI_UNICODE}
  FFileW := TGpTextFile.CreateEx(FileName, FILE_ATTRIBUTE_NORMAL, GENERIC_READ, FILE_SHARE_READ);
  FFileW.Reset;
  FFileW.Codepage := ToCodepage(FEncoding);
  FColumnsW := TWideStringList.Create;
{$ELSE}
  AssignFile(FFile, FileName);
  Reset(FFile);
  FColumns := TStringList.Create;
{$ENDIF}
  FColumnsHash := THashTable.Create(True);

  FColumnsHash.TableSize := Map.Count;
  FColumnsHash.DisposeData := DisposeColumnsHashData;

  FPosDataHash := THashTable.Create(True);
  FPosDataHash.TableSize := Map.Count;
  FPosDataHash.DisposeData := DisposePosHashData;

  for i := 0 to Map.Count - 1 do
  begin
    if FImportRow.MapNameIdxHash.Search(Map.Names[i], pi) then
    begin
      k := Integer(pi);
{$IFDEF VCL7}
      str := Map.ValueFromIndex[k];
{$ELSE}
      str := Map.Values[Map.Names[k]];
{$ENDIF}
      New(posData);
      posData^.Pos := StrToIntDef(Copy(str, 1, Pos(';', str) - 1), 0);
      posData^.Len := StrToIntDef(Copy(str, Pos(';', str) + 1, Length(str)), 0);
      FPosDataHash.Insert(Map.Names[i], posData);
    end;
  end;

  FCounter := 0;
end;

function TQImport3ASCII.CheckCondition: Boolean;
begin
{$IFDEF QI_UNICODE}
  Result := not FFileW.Eof;
{$ELSE}
  Result := not Eof(FFile);
{$ENDIF}  
end;

function TQImport3ASCII.Skip: Boolean;

  function BuffEmpty: Boolean;
  begin
  {$IFDEF QI_UNICODE}
    Result := Trim(FBuffStrW) = '';
  {$ELSE}
    Result := Trim(FBuffStr) = '';
  {$ENDIF}
  end;
  
begin
{$IFDEF QI_UNICODE}
  FBuffStrW := FFileW.ReadLn;
  if not HasComma then
    ReplaceTabs(FBuffStrW);
{$ELSE}
  Readln(FFile, FBuffStr);
  if not HasComma then
    ReplaceTabs(FBuffStr);
{$ENDIF}
  Result := (SkipFirstRows > 0) and (FCounter < SkipFirstRows);
  if Result then
    Inc(FCounter);
  Result := Result or BuffEmpty;
end;

procedure TQImport3ASCII.FillImportRow;
var
  p: Pointer;
  j, k: Integer;
  ps: PStrData;
begin
  FImportRow.ClearValues;
  FColumnsHash.Empty();

{$IFDEF QI_UNICODE}
  ReadColumns(FBuffStrW, FColumnsW, FColumnsHash);
{$ELSE}
  ReadColumns(FBuffStr, FColumns, FColumnsHash);
{$ENDIF}

  for j := 0 to FImportRow.Count - 1 do
  begin
    if FImportRow.MapNameIdxHash.Search(FImportRow[j].Name, p) then
    begin
      k := Integer(p);
{$IFDEF QI_UNICODE}
      try
        if HasComma then
{$IFDEF VCL7}
          FBuffStrW := FColumnsW[StrToInt(Map.ValueFromIndex[k]) - 1]
{$ELSE}
          FBuffStrW := FColumnsW[StrToInt(Map.Values[Map.Names[k]]) - 1]
{$ENDIF}
        else begin
          ps := FColumnsHash.Examine(FImportRow[j].Name);
          if ps <> nil then
            FBuffStrW := ps^.Str;
        end;
      except
        FBuffStrW := '';
      end;
      FImportRow.SetValue(Map.Names[k], FBuffStrW, False);
{$ELSE}
      try
        if HasComma then
{$IFDEF VCL7}
          FBuffStr := FColumns[StrToInt(Map.ValueFromIndex[k]) - 1]
{$ELSE}
          FBuffStr := FColumns[StrToInt(Map.Values[Map.Names[k]]) - 1]
{$ENDIF}
        else begin
          ps := FColumnsHash.Examine(FImportRow[j].Name);
          if ps <> nil then
            FBuffStr := ps^.Str;
        end;
      except
        FBuffStr := '';
      end;
      FImportRow.SetValue(Map.Names[k], FBuffStr, False);
{$ENDIF}
    end;
    DoUserDataFormat(FImportRow[j]);
  end;
end;

function TQImport3ASCII.ImportData: TQImportResult;
begin
  Result := qirOk;
  try
    try
      if Canceled and not CanContinue then
      begin
        Result := qirBreak;
        Exit;
      end;

      DataManipulation;

    except
      on E:Exception do
      begin
        try
          DestinationCancel;
        except
        end;
        DoImportError(E);
        Result := qirContinue;
        Exit;
      end;
    end;
  finally
    if (not IsCSV) and (CommitRecCount > 0) and not CommitAfterDone and
       (
        ((ImportedRecs + ErrorRecs) > 0)
        and ((ImportedRecs + ErrorRecs) mod CommitRecCount = 0)
       )
    then
      DoNeedCommit;
    if (ImportRecCount > 0) and
       ((ImportedRecs + ErrorRecs) mod ImportRecCount = 0) then
      Result := qirBreak;
  end;
end;

procedure TQImport3ASCII.ChangeCondition;
begin
end;

procedure TQImport3ASCII.FinishImport;
begin
  try
    if not Canceled and not IsCSV then
    begin
      if CommitAfterDone then
        DoNeedCommit
      else if (CommitRecCount > 0) and ((ImportedRecs + ErrorRecs) mod CommitRecCount > 0) then
        DoNeedCommit;
    end;
  finally
{$IFDEF QI_UNICODE}
    if Assigned(FColumnsW) then
      FColumnsW.Free;
    FFileW.Free;
{$ELSE}
    if Assigned(FColumns) then
      FColumns.Free;
    CloseFile(FFile);
{$ENDIF}
    FColumnsHash.Free;
    FPosDataHash.Free;
  end;
end;

procedure TQImport3ASCII.DoLoadConfiguration(IniFile: TIniFile);
begin
  inherited;
  with IniFile do
  begin
    SkipFirstRows := ReadInteger(ASCII_OPTIONS, ASCII_SKIP_LINES, SkipFirstRows);
    Encoding := TQICharsetType(ReadInteger(ASCII_OPTIONS, ASCII_ENCODING, Integer(Encoding)));
    Comma := Str2Char(ReadString(ASCII_OPTIONS, ASCII_COMMA,
      Char2Str(Comma)), Comma);
    Quote := Str2Char(ReadString(ASCII_OPTIONS, ASCII_QUOTE,
      Char2Str( Quote)), Quote);
  end;
end;

procedure TQImport3ASCII.DoSaveConfiguration(IniFile: TIniFile);
begin
  inherited;
  with IniFile do
  begin
    WriteInteger(ASCII_OPTIONS, ASCII_SKIP_LINES, SkipFirstRows);
    WriteInteger(ASCII_OPTIONS, ASCII_ENCODING, Integer(Encoding));
    WriteString(ASCII_OPTIONS, ASCII_COMMA, string(Comma));
    WriteString(ASCII_OPTIONS, ASCII_QUOTE, string(Quote));
  end;
end;

function TQImport3ASCII.HasComma: Boolean;
begin
  Result := FComma <> #0;
end;

procedure TQImport3ASCII.ReadColumns(
  const Str: qiString;
  AStrings: TqiStrings;
  AColumnsHash: THashTable);
var
  P, L, i: Integer;
  pi: Pointer;
  ps: PStrData;
begin
  if HasComma then
  begin
    CSVStringToStrings(Str, Quote, Comma, AStrings);
  end
  else begin
    for i := 0 to Map.Count - 1 do
    begin
      if FPosDataHash.Search(Map.Names[i], pi) then
      begin
        P := PPosData(pi)^.Pos;
        L := PPosData(pi)^.Len;
        New(ps);
        ps^.Str := Copy(Str, P + 1, L);
        AColumnsHash.Insert(Map.Names[i], ps);
      end;
    end;
  end;
end;

end.
